package dcedit.util;

// Import Java SDK Classes
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.math.*;
import java.util.ArrayDeque;

/** Calculator class */
public class Calculator extends JDialog {
   /** Serial Version UID version for serializable classes */
   public static final long serialVersionUID = 1303240915165L;

   /** Grid constants field */
   private final GridBagConstraints GRID_CONST = new GridBagConstraints();

   /** Display field */
   private final DisplayField DISPLAY = new DisplayField("0");

   /** Stack field */
   private final CalcStack STACK = new CalcStack(2);

   /** Command invoked field */
   private boolean commandInvoked = false;

   /** Store field */
   private String store = "";

   /** Constructor */
   public Calculator() {
      setTitle("Calculator");
      getContentPane().setLayout(new GridBagLayout());
      GRID_CONST.fill = GridBagConstraints.BOTH;
      GRID_CONST.insets = new Insets(3,3,3,3); // top,left,bottom,right
      DISPLAY.setHorizontalAlignment(JTextField.RIGHT);
      DISPLAY.setEditable(false);
      DISPLAY.setFont(DISPLAY.getFont().deriveFont(Font.BOLD));
      init();
      pack();
      setLocationRelativeTo(null);
      setAlwaysOnTop(true);
   } // END constructor

   /** setGridConst method */
   private void setGridConst(int x, int y, int w, int h) {
      GRID_CONST.gridx = x;
      GRID_CONST.gridy = y;
      GRID_CONST.gridwidth = w;
      GRID_CONST.gridheight = h;
   } // END setGridConst method

   /** createBtn method */
   private JButton createBtn(String text) {
      final String TEXT = "<html><b>" + text + "</b></html>";
      final JButton BTN = new JButton(TEXT);
      final Insets BTN_PAD = new Insets(0, 0, 0, 0);
      final Dimension BTN_SIZE = new Dimension(50, 20);

      BTN.setMargin(BTN_PAD);
      BTN.setMinimumSize(BTN_SIZE);
      BTN.setPreferredSize(BTN_SIZE);
      BTN.setMaximumSize(BTN_SIZE);

      return BTN;
   } // END createBtn

   /** createNumBtn method */
   private void createNumBtn(int num) {
      final String str = String.valueOf(num);
      getContentPane().add(createBtn(str, new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            if(commandInvoked) {
               DISPLAY.setText(str);
               commandInvoked = false;
            } else {
               DISPLAY.insert(str);
            } // END if/else
         } // END actionPerformed
      }), GRID_CONST);
   } // END 

   /** createBtn method */
   private JButton createBtn(String text, ActionListener al) {
      final JButton BTN = createBtn(text);
      BTN.addActionListener(al);
      return BTN;
   } // END createBtn method

   /** init method */
   private void init() {
      // Add display to the dialog.
      setGridConst(0, 0, 5, 1);
      getContentPane().add(DISPLAY, GRID_CONST);

      // Add memory clear to the dialog.
      setGridConst(0, 1, 1, 1);
      getContentPane().add(createBtn("MC", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            store = "";
         } // END actionPerformed
      }), GRID_CONST);

      // Add memory recall to the dialog.
      setGridConst(1, 1, 1, 1);
      getContentPane().add(createBtn("MR", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            if(!store.isEmpty()) DISPLAY.setText(store);
         } // END actionPerformed
      }), GRID_CONST);

      // Add memory store to the dialog.
      setGridConst(2, 1, 1, 1);
      getContentPane().add(createBtn("MS", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            store = DISPLAY.getText();
         } // END actionPerformed
      }), GRID_CONST);

      // Add memory add to the dialog.
      setGridConst(3, 1, 1, 1);
      getContentPane().add(createBtn("M+", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            store = Command.ADD.applyCommand(store, DISPLAY.getText());
         } // END actionPerformed
      }), GRID_CONST);

      // Add memory subtract to the dialog.
      setGridConst(4, 1, 1, 1);
      getContentPane().add(createBtn("M-", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            store = Command.SUB.applyCommand(store, DISPLAY.getText());
         } // END actionPerformed
      }), GRID_CONST);

      // Add separator to the dialog.
      setGridConst(0, 2, 5, 1);
      getContentPane().add(new JSeparator(), GRID_CONST);

      // Add little e to the dialog.
      setGridConst(0, 3, 1, 2);
      getContentPane().add(createBtn("e", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            DISPLAY.setText(String.valueOf(Math.E));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add pi to the dialog.
      setGridConst(1, 3, 1, 2);
      getContentPane().add(createBtn("&pi;", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            DISPLAY.setText(String.valueOf(Math.PI));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add sine to the dialog.
      setGridConst(2, 3, 1, 1);
      getContentPane().add(createBtn("sin", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.sin(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add cosine to the dialog.
      setGridConst(3, 3, 1, 1);
      getContentPane().add(createBtn("cos", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.cos(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add tangent to the dialog.
      setGridConst(4, 3, 1, 1);
      getContentPane().add(createBtn("tan", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.tan(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add asine to the dialog.
      setGridConst(2, 4, 1, 1);
      getContentPane().add(createBtn("asin", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.asin(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add acosine to the dialog.
      setGridConst(3, 4, 1, 1);
      getContentPane().add(createBtn("acos", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.acos(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add atangent to the dialog.
      setGridConst(4, 4, 1, 1);
      getContentPane().add(createBtn("atan", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.atan(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add separator to the dialog.
      setGridConst(0, 5, 5, 1);
      getContentPane().add(new JSeparator(), GRID_CONST);

      // Add backspace to the dialog.
      setGridConst(0, 6, 1, 1);
      getContentPane().add(createBtn("&larr;", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            DISPLAY.popRight();
         } // END actionPerformed
      }), GRID_CONST);

      // Add clear entry to the dialog.
      setGridConst(1, 6, 1, 1);
      getContentPane().add(createBtn("CE", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            DISPLAY.clear();
         } // END actionPerformed
      }), GRID_CONST);

      // Add clear to the dialog.
      setGridConst(2, 6, 1, 1);
      getContentPane().add(createBtn("C", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            DISPLAY.clear();
            STACK.clear();
         } // END actionPerformed
      }), GRID_CONST);

      // Add plus/minus to the dialog.
      setGridConst(3, 6, 1, 1);
      getContentPane().add(createBtn("&plusmn;", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            if(!DISPLAY.equals("0")) {
               if(DISPLAY.contains("-")) {
                  DISPLAY.popLeft();
               } else {
                  DISPLAY.prepend("-");
               } // END if/else
            } // END if
         } // END actionPerformed
      }), GRID_CONST);

      // Add power to the dialog.
      setGridConst(4, 6, 1, 1);
      getContentPane().add(createBtn("x<sup>y</sup>", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            System.out.print("\n");
            if(STACK.isEmpty()) {
               // STACK = [][]
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.POW);
               DISPLAY.clear();
            } else if(STACK.size() == 1) {
               // STACK = [###][]
               STACK.pushStack(Command.POW);
               DISPLAY.clear();
            } else {
               // STACK = [###][Command]
               Command cmd = (Command)STACK.popStack();
               String left = (String)STACK.popStack();
               String right = DISPLAY.getText();
               String result = cmd.applyCommand(left, right);
               DISPLAY.setText(result);
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.POW);
            } // END if/else
            commandInvoked = true;
         } // END actionPerformed
      }), GRID_CONST);

      // Add the number 7 to the dialog.
      setGridConst(0, 7, 1, 1);
      createNumBtn(7);

      // Add the number 8 to the dialog.
      setGridConst(1, 7, 1, 1);
      createNumBtn(8);

      // Add the number 9 to the dialog.
      setGridConst(2, 7, 1, 1);
      createNumBtn(9);

      // Add divide to the dialog.
      setGridConst(3, 7, 1, 1);
      getContentPane().add(createBtn("&divide;", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            System.out.print("\n");
            if(STACK.isEmpty()) {
               // STACK = [][]
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.DIV);
               DISPLAY.clear();
            } else if(STACK.size() == 1) {
               // STACK = [###][]
               STACK.pushStack(Command.DIV);
               DISPLAY.clear();
            } else {
               // STACK = [###][Command]
               Command cmd = (Command)STACK.popStack();
               String left = (String)STACK.popStack();
               String right = DISPLAY.getText();
               String result = cmd.applyCommand(left, right);
               DISPLAY.setText(result);
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.DIV);
            } // END if/else
            commandInvoked = true;
         } // END actionPerformed
      }), GRID_CONST);

      // Add square root to the dialog.
      setGridConst(4, 7, 1, 1);
      getContentPane().add(createBtn("&radic;&macr;", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            double val = Double.parseDouble(DISPLAY.getText());
            val = Math.sqrt(val);
            DISPLAY.setText(String.valueOf(val));
            commandInvoked = false;
         } // END actionPerformed
      }), GRID_CONST);

      // Add the number 4 to the dialog.
      setGridConst(0, 8, 1, 1);
      createNumBtn(4);

      // Add the number 5 to the dialog.
      setGridConst(1, 8, 1, 1);
      createNumBtn(5);

      // Add the number 6 to the dialog.
      setGridConst(2, 8, 1, 1);
      createNumBtn(6);

      // Add multiply to the dialog.
      setGridConst(3, 8, 1, 1);
      getContentPane().add(createBtn("&times;", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            System.out.print("\n");
            if(STACK.isEmpty()) {
               // STACK = [][]
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.MULT);
               DISPLAY.clear();
            } else if(STACK.size() == 1) {
               // STACK = [###][]
               STACK.pushStack(Command.MULT);
               DISPLAY.clear();
            } else {
               // STACK = [###][Command]
               Command cmd = (Command)STACK.popStack();
               String left = (String)STACK.popStack();
               String right = DISPLAY.getText();
               String result = cmd.applyCommand(left, right);
               DISPLAY.setText(result);
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.MULT);
            } // END if/else
            commandInvoked = true;
         } // END actionPerformed
      }), GRID_CONST);

      // Add equal to the dialog.
      setGridConst(4, 8, 1, 3);
      getContentPane().add(createBtn("=", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            if(STACK.size() == 2) {
               // STACK = [###][Command]
               System.out.print("\n");
               Command cmd = (Command)STACK.popStack();
               String left = (String)STACK.popStack();
               String right = DISPLAY.getText();
               String result = cmd.applyCommand(left, right);
               DISPLAY.setText(result);
            } // END if/else
         } // END actionPerformed
      }), GRID_CONST);

      // Add the number 1 to the dialog.
      setGridConst(0, 9, 1, 1);
      createNumBtn(1);

      // Add the number 2 to the dialog.
      setGridConst(1, 9, 1, 1);
      createNumBtn(2);

      // Add the number 3 to the dialog.
      setGridConst(2, 9, 1, 1);
      createNumBtn(3);

      // Add minus to the dialog.
      setGridConst(3, 9, 1, 1);
      getContentPane().add(createBtn("-", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            System.out.print("\n");
            if(STACK.isEmpty()) {
               // STACK = [][]
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.SUB);
               DISPLAY.clear();
            } else if(STACK.size() == 1) {
               // STACK = [###][]
               STACK.pushStack(Command.SUB);
               DISPLAY.clear();
            } else {
               // STACK = [###][Command]
               Command cmd = (Command)STACK.popStack();
               String left = (String)STACK.popStack();
               String right = DISPLAY.getText();
               String result = cmd.applyCommand(left, right);
               DISPLAY.setText(result);
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.SUB);
            } // END if/else
            commandInvoked = true;
         } // END actionPerformed
      }), GRID_CONST);

      // Add the number 0 to the dialog.
      setGridConst(0, 10, 2, 1);
      createNumBtn(0);

      // Add decimal to the dialog.
      setGridConst(2, 10, 1, 1);
      getContentPane().add(createBtn(".", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            if(!DISPLAY.contains(".")) DISPLAY.append(".");
         } // END actionPerformed
      }), GRID_CONST);

      // Add plus to the dialog.
      setGridConst(3, 10, 1, 1);
      getContentPane().add(createBtn("+", new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent ae) {
            System.out.print("\n");
            if(STACK.isEmpty()) {
               // STACK = [][]
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.ADD);
               DISPLAY.clear();
            } else if(STACK.size() == 1) {
               // STACK = [###][]
               STACK.pushStack(Command.ADD);
               DISPLAY.clear();
            } else {
               // STACK = [###][Command]
               Command cmd = (Command)STACK.popStack();
               String left = (String)STACK.popStack();
               String right = DISPLAY.getText();
               String result = cmd.applyCommand(left, right);
               DISPLAY.setText(result);
               STACK.pushStack(DISPLAY.getText());
               STACK.pushStack(Command.ADD);
            } // END if/else
            commandInvoked = true;
         } // END actionPerformed
      }), GRID_CONST);
   } // END method init

   //////////////////////
   // INNER CLASSES, ETC.
   //////////////////////

   /** Inner class CalcStack */
   public class CalcStack extends ArrayDeque<Object> {
      /** Serial Version UID version for serializable classes */
      public static final long serialVersionUID = 1303314420482L;

      /** Constructor */
      public CalcStack() {
         super();
      } // END constructor

      /** Constructor */
      public CalcStack(int numElements) {
         super(numElements);
      } // END constructor

      /** push method */
      public void pushStack(Object item) {
         System.out.print("ArrayDeque::pushStack(" + item + "): " + this);
         if(item instanceof String && "undefined".equals((String)item)) super.push("0");
         else if(item instanceof String && "NaN".equals((String)item)) super.push("0");
         else super.push(item);
         System.out.println("=>" + this);
      } // END push method

      /** pop method */
      public Object popStack() {
         System.out.print("ArrayDeque::popStack(): " + this);
         Object item = super.pop();
         System.out.println("=>" + this);
         return item;
      } // END pop method
   } // END inner class CalcStack

   /** Inner enum Command */
   public enum Command {
      POW("pow"),
      ADD("add"),
      SUB("sub"),
      MULT("mult"),
      DIV("div");

      /** Name field */
      private String name;

      /** Constructor */
      private Command(String name) {
         this.name = name;
      } // END constructor

      /** toString method */
      public String toString() {
         return this.name;
      } // END toString method

      /** applyCommand method */
      public String applyCommand(String left, String right) {
         try {
            return applyCommand(new BigDecimal(left),new BigDecimal(right)).toString();
         } catch(ArithmeticException ae) {
            return "undefined";
         } // END try/catch
      } // END applyCommand method

      /** applyCommand method */
      public BigDecimal applyCommand(BigDecimal left, BigDecimal right) throws ArithmeticException {
         System.out.println(this + ".applyCommand(" + left + "," + right + ")");
         if(null == left || null == right) {
            return BigDecimal.ZERO;
         } else if(this == POW) {
            return left.pow(right.intValue());
         } else if(this == ADD) {
            return left.add(right);
         } else if(this == SUB) {
            return left.subtract(right);
         } else if(this == MULT) {
            return left.multiply(right);
         } else if(this == DIV) {
            return left.divide(right);
         } else {
            return BigDecimal.ZERO;
         } // END if/else
      } // END applyCommand method
   }; // END inner enum Command

   /** Inner class DisplayField */
   public class DisplayField extends JTextField {
      /** Serial Version UID version for serializable classes */
      public static final long serialVersionUID = 1303246723093L;

      /** Constructor */
      public DisplayField() {
         super();
      } // END constructor

      /** Constructor */
      public DisplayField(String text) {
         super(text);
      } // END constructor

      /** equals method */
      public boolean equals(String text) {
         return getText().equals(text);
      } // END equals method

      /** contains method */
      public boolean contains(String text) {
         return getText().contains(text);
      } // END contains method

      /** length method */
      public int length() {
         return getText().length();
      } // END length method

      /** clear method */
      public void clear() {
         setText("0");
      } // END clear method

      /** insert method */
      public void insert(String text) {
         if(equals("0")) setText(text);
         else append(text);
      } // END insert method

      /** prepend method */
      public void prepend(String text) {
         setText(text + getText());
      } // END prepend method

      /** append method */
      public void append(String text) {
         setText(getText() + text);
      } // END append method

      /** popLeft method */
      public void popLeft() {
         if(2 > length()) clear();
         else setText(substring(1));
      } // END popLeft method

      /** popRight method */
      public void popRight() {
         if(2 > length()) clear();
         else setText(substring(0, length() - 1));
      } // END popRight method

      /** substring method */
      private String substring(int begin) {
         return getText().substring(begin);
      } // END substring method

      /** substring method */
      private String substring(int begin, int end) {
         return getText().substring(begin, end);
      } // END substring method
   } // END inner class DisplayField
} // END class Calculator
